<?php
/***********************************************************************
 * @package     IvanEngine
 * @subpackage  Core
 * @author      Ivan <ivanzx@msn.com>
 * @created     2010-06-06
 ***********************************************************************/
if(defined("IVANENGINE_CORE_URLHELPER"))
    die("Redefinition macro in :".__FILE__."(".__LINE__.")");
define("IVANENGINE_CORE_URLHELPER", true);

require_once (dirname(__FILE__).'/../Common.php');
require_once (dirname(__FILE__).'/../Routing/Routing.php');

class CUrlHelper extends CBase
{
    static protected $ms_routing    = null;
    
    public function __construct()
    {
        parent::__construct();
    }
    
    public function __destruct()
    {
        parent::__destruct();
    }
    
    static public function CorrectUrl($Url)
    {
        return str_replace(M_DS, '/', $Url);
    }
    
    static public function U($Url)
    {
        return self::CorrectUrl($Url);
    }
    
    static public function SetRouting(CRouting & $routing)
    {
        self::$ms_routing   = $routing;
    }
    
    static public function LinkTo($url, $name = '', $options = array())
    {
        $html_options = CTagHelper::_ParseAttributes($options);

        $absolute     = false;
        if (isset($html_options['absolute']))
        {
            $absolute = (boolean) $html_options['absolute'];
            unset($html_options['absolute']);
        }

        $ajax     = null;
        if (isset($html_options['ajax']))
        {
            $ajax = $html_options['ajax'];
            $ajax = CStringHelper::ToLower($ajax);
            unset($html_options['ajax']);
        }
            
        $resultUrl    = self::UrlFor($url, $absolute);

        $html_options = self::_ConvertOptionsToJavascript($html_options, $url);

        if (isset($html_options['query']))
        {
            if( CStringHelper::FindLastOf('?', $resultUrl) != CString::npos || 
                CStringHelper::FindLastOf('&', $resultUrl) != CString::npos )
            {
                $resultUrl = $resultUrl.'&'.$html_options['query'];
            }else{
                $resultUrl = $resultUrl.'?'.$html_options['query'];
            }
            unset($html_options['query']);
        }
        
        if (isset($html_options['anchor']))
        {
            $html_options['href'] .= '#'.$html_options['anchor'];
            unset($html_options['anchor']);
        }else{
            if( $ajax ){
                $ajaxOption = array('ajax' => $ajax );
                if( isset($html_options['window']) ){
                    $ajaxOption['window'] = $html_options['window'];
                    unset($html_options['window']);
                }
                if( isset($html_options['function']) ){
                    $ajaxOption['function'] = $html_options['function'];
                    unset($html_options['function']);
                }
                $html_options['onclick'] = self::AjaxTo($resultUrl, $ajaxOption);
                $html_options['href'] = 'javascript:;';
            }else if(isset($html_options['onclick'])){
                $html_options['href'] = 'javascript:;';
            }else{
                $html_options['href'] = $resultUrl;
            }
        }

        if (is_object($name))
        {
            if (method_exists($name, '__toString'))
            {
                $name = $name->__toString();
            }
            else
            {
                throw new CException(
                    sprintf('Object of class "%s" cannot be converted to string (Please create a GetName() method).', get_class($name)));
            }
        }

        if (!strlen($name))
        {
            $name = $resultUrl;
        }

        return CTagHelper::ContentTag('a', $name, $html_options);
    }

    static public function UrlFor($parameters, $absolute = false)
    {
         // absolute URL or symfony URL?
        if (is_string($parameters) && 
            preg_match('#^[a-z][a-z0-9\+.\-]*\://#i', $parameters))
        {
            return $parameters;
        }

        // relative URL?
        if (is_string($parameters) && 0 === strpos($parameters, '/'))
        {
            return $parameters;
        }

        if (is_string($parameters) && $parameters == '#')
        {
            return $parameters;
        }

        $route = '';
        $fragment = '';

        if (is_string($parameters))
        {
            // strip fragment
            if (false !== ($pos = strpos($parameters, '#')))
            {
                $fragment = substr($parameters, $pos + 1);
                $parameters = substr($parameters, 0, $pos);
            }

            list($route, $parameters) = self::_ConvertUrlStringToParameters($parameters);
        }
        else if (is_array($parameters))
        {
            if (isset($parameters['route']))
            {
                $route = $parameters['route'];
                unset($parameters['route']);
            }
        }

        // routing to generate path
        if( self::$ms_routing == null ){
            throw new CExceptionInvalidArgument(
                'You must set routing with use CUrlHelper::SetRouting().');
        }
        $url = self::$ms_routing->Generate($route, $parameters, $absolute);

        if ($fragment)
        {
            $url .= '#'.$fragment;
        }

        return $url;
    }
    
    static public function AjaxTo($url, $parameters = array('ajax' => 'get'))
    {
        if( !isset($parameters['ajax']) && 
            !isset($parameters['function']) &&
            !isset($parameters['window']) )
        {
            return $url;
        }else if( !isset($parameters['ajax']) ){
            $parameters['ajax'] = 'get';
        }
        
        {
            $method = $parameters['ajax'];
            unset($parameters['ajax']);
            $method = HString::ToLower($method);
            if( $method != 'get' && $method != 'post' ){
                throw new CExceptionInvalidArgument(
                    'method parameter only is get/post!');
            }
        }
        $function   = 'function(responseText, textStatus){}';
        if( isset($parameters['function']) ){
            $function = $parameters['function'];
            unset($parameters['function']);
        }else if( isset($parameters['window']) ){
            $function= 'function(responseText, textStatus){ '.
                '$(\'#'.$parameters['window'].'\').html(responseText);}';
            unset($parameters['window']);
        }
        $options  = array();
        foreach( $parameters as $key => $val ){
            $options[]    = $key.':\''.$val.'\'';
        }
        
        $content = '$.'.$method.'(\''.$url.'\', {';
        $content.= implode(',', $options).'},'.$function.');';
        return $content;
    }

    /**
     * If the condition passed as first argument is true,
     * creates a <a> link Tag of the given name using a routed URL
     * based on the plugin/action passed as argument and the routing configuration.
     * If the condition is false, the given name is returned between <span> Tags
     *
     * <b>Options:</b>
     * - 'Tag' - the HTML Tag that must enclose the name if the condition is false, defaults to <span>
     * - 'absolute' - if set to true, the helper outputs an absolute URL
     * - 'query' - to append a query string (starting by ?) to the routed url
     * - 'anchor' - to append an anchor (starting by #) to the routed url
     * - 'confirm' - displays a javascript confirmation alert when the link is clicked
     * - 'popup' - if set to true, the link opens a new browser window 
     * - 'post' - if set to true, the link submits a POST request instead of GET (caution: do not use inside a form)
     *
     * <b>Examples:</b>
     * <code>
     *  echo linkToIf($user->IsAdministrator(), 'Delete this page', 'my_plugin/my_action');
     *    => <a href="/path/to/my/action">Delete this page</a>
     *  echo linkToIf(!$user->IsAdministrator(), 'Delete this page', 'my_plugin/my_action'); 
     *    => <span>Delete this page</span>
     * </code>
     *
     * @param  bool   $condition     condition
     * @param  string $name          name of the link, i.e. string to appear between the <a> Tags
     * @param  string $internal_uri  'plugin/action' or '@rule' of the action
     * @param  array  $options       additional HTML compliant <a> Tag parameters
     * @return string XHTML compliant <a href> Tag or name
     * @see    LinkTo
     */
    static public function LinkToIf($condition, $internal_uri, $name = '', $options = array())
    {
        $html_options = CTagHelper::_ParseAttributes($options);
        if ($condition)
        {
            unset($html_options['Tag']);
            return self::LinkTo($name, $internal_uri, $html_options);
        }
        else
        {
            unset($html_options['query']);
            unset($html_options['absolute_url']);
            unset($html_options['absolute']);

            $Tag = CTagHelper::_GetOption($html_options, 'Tag', 'span');

            return CTagHelper::ContentTag($Tag, $name, $html_options);
        }
    }

    /**
     * If the condition passed as first argument is false,
     * creates a <a> link Tag of the given name using a routed URL
     * based on the plugin/action passed as argument and the routing configuration.
     * If the condition is true, the given name is returned between <span> Tags
     *
     * <b>Options:</b>
     * - 'Tag' - the HTML Tag that must enclose the name if the condition is true, defaults to <span>
     * - 'absolute' - if set to true, the helper outputs an absolute URL
     * - 'query' - to append a query string (starting by ?) to the routed url
     * - 'anchor' - to append an anchor (starting by #) to the routed url
     * - 'confirm' - displays a javascript confirmation alert when the link is clicked
     * - 'popup' - if set to true, the link opens a new browser window 
     * - 'post' - if set to true, the link submits a POST request instead of GET (caution: do not use inside a form)
     *
     * <b>Examples:</b>
     * <code>
     *  echo link_to_unless($user->IsAdministrator(), 'Delete this page', 'my_plugin/my_action');
     *    => <span>Delete this page</span>
     *  echo link_to_unless(!$user->IsAdministrator(), 'Delete this page', 'my_plugin/my_action'); 
     *    => <a href="/path/to/my/action">Delete this page</a>
     * </code>
     *
     * @param  bool   $condition     condition
     * @param  string $name          name of the link, i.e. string to appear between the <a> Tags
     * @param  string $internal_uri  'plugin/action' or '@rule' of the action
     * @param  array  $options       additional HTML compliant <a> Tag parameters
     * @return string XHTML compliant <a href> Tag or name
     * @see    LinkTo
     */
    static public function LinkToUnless($condition, $name = '', $internal_uri, $options = array())
    {
        return self::LinkToIf(!$condition, $name, $internal_uri, $options);
    }

    /**
     * Creates an <input> button Tag of the given name pointing to a routed URL
     * based on the plugin/action passed as argument and the routing configuration.
     * The syntax is similar to the one of link_to.
     *
     * <b>Options:</b>
     * - 'absolute' - if set to true, the helper outputs an absolute URL
     * - 'query' - to append a query string (starting by ?) to the routed url
     * - 'anchor' - to append an anchor (starting by #) to the routed url
     * - 'confirm' - displays a javascript confirmation alert when the button is clicked
     * - 'popup' - if set to true, the button opens a new browser window 
     * - 'post' - if set to true, the button submits a POST request instead of GET (caution: do not use inside a form)
     *
     * <b>Examples:</b>
     * <code>
     *  echo button_to('Delete this page', 'my_plugin/my_action');
     *    => <input value="Delete this page" type="button" onclick="document.location.href='/path/to/my/action';" />
     * </code>
     *
     * @param  string $name          name of the button
     * @param  string $internal_uri  'plugin/action' or '@rule' of the action
     * @param  array  $options       additional HTML compliant <input> Tag parameters
     * @return string XHTML compliant <input> Tag
     * @see    UrlFor, link_to
     */
    static public function ButtonTo($internal_uri, $name = '', $options = array())
    {
        $html_options = CTagHelper::_ParseAttributes($options);
        $html_options['value'] = $name;

        $ajax     = null;
        if (isset($html_options['ajax']))
        {
            $ajax = $html_options['ajax'];
            $ajax = CStringHelper::ToLower($ajax);
            unset($html_options['ajax']);
        }
        
        $url = self::UrlFor($internal_uri);
        if (isset($html_options['query']))
        {
            if( strpos('&', $url) > 0 || strpos('?', $url) > 0){
                $url = $url.'&'.$html_options['query'];
            }else{
                $url = $url.'?'.$html_options['query'];
            }
            unset($html_options['query']);
        }
        if (isset($html_options['anchor']))
        {
            $url = $url.'#'.$html_options['anchor'];
            unset($html_options['anchor']);
        }
        
        $html_options['type'] = 'button';

        if (isset($html_options['popup']))
        {
            $html_options = self::_ConvertOptionsToJavascript($html_options, $url);
            unset($html_options['popup']);
        }else{
            if( $ajax ){
                $ajaxOption = array('ajax' => $ajax );
                if( isset($html_options['window']) ){
                    $ajaxOption['window'] = $html_options['window'];
                    unset($html_options['window']);
                }
                if( isset($html_options['function']) ){
                    $ajaxOption['function'] = $html_options['function'];
                    unset($html_options['function']);
                }
                $html_options['onclick'] = self::AjaxTo($url, $ajaxOption);
            }else{
                $html_options['onclick'] = "document.location.href=".$url.";";
            }
            $html_options = self::_ConvertOptionsToJavascript($html_options);
        }

        return CTagHelper::Tag('input', $html_options);
    }

    /**
     * Creates a <a> link Tag to the given email (with href="mailto:...").
     * If null is passed as a name, the email itself will become the name.
     *
     * <b>Options:</b>
     * - 'encode' - if set to true, the email address appears with various random encoding for each letter.
     * The mail link still works when encoded, but the address doesn't appear in clear
     * in the source. Use it to prevent spam (efficiency not guaranteed).
     *
     * <b>Examples:</b>
     * <code>
     *  echo MailTo('webmaster@example.com');
     *    => <a href="mailto:webmaster@example.com">webmaster@example.com</a>
     *  echo MailTo('webmaster@example.com', 'send us an email');
     *    => <a href="mailto:webmaster@example.com">send us an email</a>
     *  echo MailTo('webmaster@example.com', 'send us an email', array('encode' => true));
     *    => <a href="
                &#x6d;a&#x69;&#x6c;&#x74;&#111;&#58;&#x77;&#x65;b&#x6d;as&#116;&#x65;&#114;
                &#64;&#101;&#x78;&#x61;&#x6d;&#x70;&#108;&#x65;&#46;&#99;&#x6f;&#109;
              ">send us an email</a>
     * </code>
     *
     * @param  string $email          target email
     * @param  string $name           name of the link, i.e. string to appear between the <a> Tags
     * @param  array  $options        additional HTML compliant <a> Tag parameters
     * @param  array  $default_value
     * @return string XHTML compliant <a href> Tag
     * @see    link_to
     */
    static public function MailTo($email, $name = '', $options = array(), $default_value = array())
    {
        $html_options = CTagHelper::_ParseAttributes($options);

        $html_options = self::_ConvertOptionsToJavascript($html_options);

        $default_tmp  = CTagHelper::_ParseAttributes($default_value);
        $default = array();
        foreach ($default_tmp as $key => $value)
        {
            $default[] = urlencode($key).'='.urlencode($value);
        }
        $options = count($default) ? '?'.implode('&', $default) : '';

        if (isset($html_options['encode']) && $html_options['encode'])
        {
            unset($html_options['encode']);
            $html_options['href'] = self::_EncodeText('mailto:'.$email.$options);
            if (!$name)
            {
                $name = self::_EncodeText($email);
            }
        }
        else
        {
            $html_options['href'] = 'mailto:'.$email.$options;
            if (!$name)
            {
                $name = $email;
            }
        }

        return CTagHelper::ContentTag('a', $name, $html_options);
    }

    static public function _ConvertOptionsToJavascript($html_options, $url = 'this.href')
    {
        // confirm
        $confirm = isset($html_options['confirm']) ? $html_options['confirm'] : '';
        unset($html_options['confirm']);

        // popup
        $popup = isset($html_options['popup']) ? $html_options['popup'] : '';
        unset($html_options['popup']);

        $onclick = isset($html_options['onclick']) ? $html_options['onclick'] : '';

        if ($confirm && $popup)
        {
            $html_options['onclick'] = $onclick.'if ('.self::_ConfirmJavascriptFunction($confirm).') { '._PopupJavascriptFunction($popup, $url).' };return false;';
        }
        else if ($confirm)
        {
            if ($onclick)
            {
                $html_options['onclick'] = 'if ('.self::_ConfirmJavascriptFunction($confirm).') { return '.$onclick.'} else return false;';
            }
            else
            {
                $html_options['onclick'] = 'return '.self::_ConfirmJavascriptFunction($confirm).';';
            }
        }
        else if ($popup)
        {
            $html_options['onclick'] = $onclick.self::_PopupJavascriptFunction($popup, $url).'return false;';
        }

        return $html_options;
    }

    static public function _ConfirmJavascriptFunction($confirm)
    {
        return "confirm('".CTagHelper::EscapeJavascript($confirm)."')";
    }

    static public function _PopupJavascriptFunction($popup, $url = '')
    {
        if (is_array($popup))
        {
            if (isset($popup[1]))
            {
                return "var w=window.open('".$url."','".$popup[0]."','".$popup[1]."');w.focus();";
            }
            else
            {
                return "var w=window.open('".$url."','".$popup[0]."');w.focus();";
            }
        }
        else
        {
            return "var w=window.open('".$url."');w.focus();";
        }
    }

    static public function _EncodeText($text)
    {
        $encoded_text = '';

        for ($i = 0; $i < strlen($text); $i++)
        {
            $char = $text{$i};
            $r = rand(0, 100);

            # roughly 10% raw, 45% hex, 45% dec
            # '@' *must* be encoded. I insist.
            if ($r > 90 && $char != '@')
            {
                $encoded_text .= $char;
            }
            else if ($r < 45)
            {
                $encoded_text .= '&#x'.dechex(ord($char)).';';
            }
            else
            {
                $encoded_text .= '&#'.ord($char).';';
            }
        }

        return $encoded_text;
    }
    
    static public function _ConvertUrlStringToParameters($url)
    {
        $givenUrl = $url;

        $params = array();
        $queryString = '';
        $route = '';

        // empty url?
        if (!$url)
        {
            $url = '/';
        }

        // we get the query string out of the url
        if ($pos = strpos($url, '?'))
        {
            $queryString = substr($url, $pos + 1);
            $url = substr($url, 0, $pos);
        }

        // 2 url forms
        // @routeName?key1=value1&key2=value2...
        // plugin/action?key1=value1&key2=value2...
        // first slash optional
        if ($url[0] == '/')
        {
            $url = substr($url, 1);
        }


        // routeName?
        if ($url[0] == '@')
        {
            $route = substr($url, 1);
        }
        else if (false !== strpos($url, '/'))
        {
            list($params['plugin'], $params['action']) = explode('/', $url);
        }
        else if (!$queryString)
        {
            $route = $givenUrl;
        }
        else
        {
            throw new CExceptionInvalidArgument(
                sprintf('An internal URI must contain a plugin and an action (plugin/action) ("%s" given).', $givenUrl));
        }

        // split the query string
        if ($queryString)
        {
            $matched = preg_match_all('/
                ([^&=]+)            # key
                =                   # =
                (.*?)               # value
                (?:
                  (?=&[^&=]+=) | $  # followed by another key= or the end of the string
                )
                /x', $queryString, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
            foreach ($matches as $match)
            {
                $params[urldecode($match[1][0])] = urldecode($match[2][0]);
            }

            // check that all string is matched
            if (!$matched)
            {
                throw new CException(
                    sprintf('Unable to parse query string "%s".', $queryString));
            }
        }

        return array($route, $params);
    }
}

class HUrl extends CUrlHelper{}
?>
